﻿/*
 * Contains the logic to populate the diagram. Populates rows with 
 * groups and nodes based on the node relationships. 
*/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
using GIALib;

namespace GIA
{
    class DiagramLogic
    {
        #region fields

        // List of the connections, specify connections between two nodes.
        private List<DiagramConnector> connections = new List<DiagramConnector>();

        // Map that allows quick lookup of a Person object to connection information.
        // Used when setting up the connections between nodes.
        private Dictionary<Person, DiagramConnectorNode> personLookup =
            new Dictionary<Person, DiagramConnectorNode>();

        // List of people, global list that is shared by all objects in the application.
        private PeopleCollection group;

        // Callback when a node is clicked.
        private RoutedEventHandler nodeClickHandler;

        // Filter year for nodes and connectors.
        private double displayYear;

        #endregion

        #region properties

        /// <summary>
        /// Sets the callback that is called when a node is clicked.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public RoutedEventHandler NodeClickHandler
        {
            set { nodeClickHandler = value; }
        }

        /// <summary>
        /// Gets the list of people in the family.
        /// </summary>
        public PeopleCollection Group
        {
            get { return group; }
        }

        /// <summary>
        /// Gets the list of connections between nodes.
        /// </summary>
        public List<DiagramConnector> Connections
        {
            get { return connections; }
        }

        /// <summary>
        /// Gets the person lookup list. This includes all of the 
        /// people and nodes that are displayed in the diagram.
        /// </summary>
        public Dictionary<Person, DiagramConnectorNode> PersonLookup
        {
            get { return personLookup; }
        }

        /// <summary>
        /// Sets the year filter that filters nodes and connectors.
        /// </summary>
        public double DisplayYear
        {
            set
            {
                if (this.displayYear != value)
                {
                    this.displayYear = value;
                    foreach (DiagramConnectorNode connectorNode in personLookup.Values)
                        connectorNode.Node.DisplayYear = this.displayYear;
                }
            }
        }

        /// <summary>
        /// Gets the minimum year in all nodes and connectors.
        /// </summary>
        public double MinimumYear
        {
            get
            {
                // Init to current year.
                double minimumYear = DateTime.Now.Year;

                // Check birth years.
                foreach (DiagramConnectorNode connectorNode in personLookup.Values)
                {
                    DateTime? date = connectorNode.Node.Person.BirthDate;
                    if (date != null)
                        minimumYear = Math.Min(minimumYear, date.Value.Year);
                }

                return minimumYear;
            }
        }

        #endregion

        public DiagramLogic()
        {
            // The list of people, this is a global list shared by the application.
            group = App.Group;

            Clear();
        }

        #region get people

        /// <summary>
        /// Return a list of seniors for the people in the specified row.
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        public static Collection<Person> GetSeniors(DiagramRow row)
        {
            // List that is returned.
            Collection<Person> list = new Collection<Person>();

            // Get possible juniors in the row.
            List<Person> rowList = GetPrimaryAndRelatedPeople(row);

            // Add each senior to the list, make sure the senior is only added once.
            foreach (Person person in rowList)
            {
                foreach (Person senior in person.Seniors)
                {
                    if (!list.Contains(senior))
                        list.Add(senior);
                }
            }

            return list;
        }

        /// <summary>
        /// Return a list of juniors for the people in the specified row.
        /// </summary>
        public static List<Person> GetJuniors(DiagramRow row)
        {
            // List that is returned.
            List<Person> list = new List<Person>();

            // Get possible seniors in the row.
            List<Person> rowList = GetPrimaryAndRelatedPeople(row);

            // Add each junior to the list, make sure the junior is only added once.
            foreach (Person person in rowList)
            {
                foreach (Person junior in person.Juniors)
                {
                    if (!list.Contains(junior))
                        list.Add(junior);
                }
            }

            return list;
        }

        /// <summary>
        /// Return list of people in the row that are primary or related node types.
        /// </summary>
        private static List<Person> GetPrimaryAndRelatedPeople(DiagramRow row)
        {
            List<Person> list = new List<Person>();
            foreach (DiagramGroup group in row.Groups)
            {
                foreach (DiagramNode node in group.Nodes)
                {
                    if (node.Type == NodeType.Related || node.Type == NodeType.Primary)
                        list.Add(node.Person);
                }
            }

            return list;
        }

        /// <summary>
        /// Remove any people from the 'other' list from the 'people' list.
        /// </summary>
        private static void RemoveDuplicates(Collection<Person> people, Collection<Person> other)
        {
            foreach (Person person in other)
                people.Remove(person);
        }

        #endregion

        #region create nodes

        /// <summary>
        /// Create a DiagramNode.
        /// </summary>
        private DiagramNode CreateNode(Person person, NodeType type, bool clickEvent, double scale)
        {
            DiagramNode node = CreateNode(person, type, clickEvent);
            node.Scale = scale;
            return node;
        }

        /// <summary>
        /// Create a DiagramNode.
        /// </summary>
        private DiagramNode CreateNode(Person person, NodeType type, bool clickEvent)
        {
            DiagramNode node = new DiagramNode();
            node.Person = person;
            node.Type = type;
            if (clickEvent)
                node.Click += nodeClickHandler;

            return node;
        }

        /// <summary>
        /// Add the workmates to the specified row and group.
        /// </summary>
        private void AddWorkmateNodes(DiagramRow row, DiagramGroup group,
            Collection<Person> workmates, NodeType nodeType, double scale)
        {
            foreach (Person workmate in workmates)
            {
                if (!personLookup.ContainsKey(workmate))
                {
                    // workmate node.
                    DiagramNode node = CreateNode(workmate, nodeType, true, scale);
                    group.Add(node);
                    personLookup.Add(node.Person, new DiagramConnectorNode(node, group, row));
                }
            }
        }

         

        #endregion

        #region create rows

        /// <summary>
        /// Creates the primary row. The row contains groups: 1) The primary-group 
        /// that only contains the primary node, and 2) The optional left-group 
        /// that contains workmates.
        /// </summary>
        public DiagramRow CreatePrimaryRow(Person person, double scale, double scaleRelated)
        {
            // The primary node contains two groups, 
            DiagramGroup primaryGroup = new DiagramGroup();
            DiagramGroup leftGroup = new DiagramGroup();

            // Set up the row.
            DiagramRow row = new DiagramRow();

            // Add primary node.
            DiagramNode node = CreateNode(person, NodeType.Primary, false, scale);
            primaryGroup.Add(node);
            personLookup.Add(node.Person, new DiagramConnectorNode(node, primaryGroup, row));

            // Workmates.
            Collection<Person> workmates = person.Workmates;
            AddWorkmateNodes(row, leftGroup, workmates, NodeType.Workmate, scaleRelated);
             

            if (leftGroup.Nodes.Count > 0)
            {
                leftGroup.Reverse();
                row.Add(leftGroup);
            }

            row.Add(primaryGroup);

            return row;
        }

        /// <summary>
        /// Create the junior row. The row contains a group for each junior. 
        /// Each group contains the junior and spouses.
        /// </summary>
        public DiagramRow CreateJuniorsRow(List<Person> juniors, double scale, double scaleRelated)
        {
            // Setup the row.
            DiagramRow row = new DiagramRow();

            foreach (Person junior in juniors)
            {
                // Each junior is in their group, the group contains the junior 
                // The groups does not contain workmates.
                DiagramGroup group = new DiagramGroup();
                row.Add(group);

                // junior.
                if (!personLookup.ContainsKey(junior))
                {
                    DiagramNode node = CreateNode(junior, NodeType.Related, true, scale);
                    group.Add(node);
                    personLookup.Add(node.Person, new DiagramConnectorNode(node, group, row));
                }
 
                // Connections.
                AddSeniorConnections(junior);

                group.Reverse();
            }

            return row;
        }

        /// <summary>
        /// Create the senior row. The row contains a group for each senior. 
        /// Each groups contains the senior and workmates.
        /// </summary>
        public DiagramRow CreateSeniorsRow(Collection<Person> seniors, double scale, double scaleRelated)
        {
            // Set up the row.
            DiagramRow row = new DiagramRow();

            int groupCount = 0;

            foreach (Person person in seniors)
            {
                // Each senior is in their group, the group contains the senior,
                // spouses and workmates.
                DiagramGroup group = new DiagramGroup();
                row.Add(group);

                // Determine if this is a left or right oriented group.
                bool left = (groupCount++ % 2 == 0) ? true : false;

                // senior.
                if (!personLookup.ContainsKey(person))
                {
                    DiagramNode node = CreateNode(person, NodeType.Related, true, scale);
                    group.Add(node);
                    personLookup.Add(node.Person, new DiagramConnectorNode(node, group, row));
                }         

                // workmates.
                Collection<Person> workmates = person.Workmates;
                AddWorkmateNodes(row, group, workmates, NodeType.Workmate, scaleRelated);

                 
                // Connections.
                AddJuniorConnections(person);
      
                if (left)
                    group.Reverse();
            }

            
            return row;
        }

        #endregion

        #region connections

        /// <summary>
        /// Add connections for each person and the person's juniors in the list.
        /// </summary>
        private void AddJuniorConnections(Collection<Person> seniors)
        {
            foreach (Person person in seniors)
                AddJuniorConnections(person);
        }

        /// <summary>
        /// Add connections between the junior and junior's seniors.
        /// </summary>
        private void AddSeniorConnections(Person junior)
        {
            foreach (Person senior in junior.Seniors)
            {
                if (personLookup.ContainsKey(senior) &&
                    personLookup.ContainsKey(junior))
                {
                    connections.Add(new JuniorDiagramConnector(
                        personLookup[senior], personLookup[junior]));
                }
            }
        }

        /// <summary>
        /// Add connections between the senior and senior抯 juniors.
        /// </summary>
        private void AddJuniorConnections(Person senior)
        {
            foreach (Person junior in senior.Juniors)
            {
                if (personLookup.ContainsKey(senior) &&
                    personLookup.ContainsKey(junior))
                {
                    connections.Add(new JuniorDiagramConnector(
                        personLookup[senior], personLookup[junior]));
                }
            }
        }

        

        #endregion

        /// <summary>
        /// Clear 
        /// </summary>
        public void Clear()
        {
            // Remove any event handlers from the nodes. Otherwise 
            // the delegate maintains a reference to the object 
            // which can hinder garbage collection. 
            foreach (DiagramConnectorNode node in personLookup.Values)
                node.Node.Click -= nodeClickHandler;

            // Clear the connection info.
            connections.Clear();
            personLookup.Clear();

            // Time filter.
            displayYear = DateTime.Now.Year;
        }

        /// <summary>
        /// Return the DiagramNode for the specified Person.
        /// </summary>
        public DiagramNode GetDiagramNode(Person person)
        {
            if (person == null)
                return null;

            if (!personLookup.ContainsKey(person))
                return null;

            return personLookup[person].Node;
        }

        /// <summary>
        /// Return the bounds (relative to the diagram) for the specified person.
        /// </summary>
        public Rect GetNodeBounds(Person person)
        {
            Rect bounds = Rect.Empty;
            if (person != null && personLookup.ContainsKey(person))
            {
                DiagramConnectorNode connector = personLookup[person];
                bounds = new Rect(connector.TopLeft.X, connector.TopLeft.Y,
                    connector.Node.ActualWidth, connector.Node.ActualHeight);
            }

            return bounds;
        }
    }
}
